package com.stark.wechat.service.user;

import com.dtflys.forest.Forest;
import com.fasterxml.jackson.databind.JsonNode;
import com.stark.wechat.core.Config;
import com.stark.wechat.core.Lang;
import com.stark.wechat.service.user.model.GetBlacklistResponsePayload;
import com.stark.wechat.service.user.model.GetFansResponsePayload;
import com.stark.wechat.service.user.model.UserInfo;
import com.stark.wechat.service.user.model.UserSearchParams;
import com.stark.wechat.util.JacksonUtils;
import com.stark.wechat.util.ResponseUtils;
import lombok.Builder;
import org.apache.commons.lang3.StringUtils;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static java.util.Objects.requireNonNull;

/**
 * 用户接口
 *
 * @author <a href="mailto:mengbin@hotmail.com">Ben</a>
 * @version 1.0.0
 * @since 2024/5/27
 */
@Builder
public class UserService {

    private final Config config;

    /**
     * 取消拉黑用户
     * <p>本接口用来取消拉黑一批用户，黑名单列表由一串OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。
     *
     * @param accessToken 调用接口凭证
     * @param openidList  需要取消拉黑的openid列表，一次最多取消20个用户
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/usermanage/userinfo/api_batchunblacklist.html">用户管理 / 用户信息 / 取消拉黑用户</a>
     */
    public void batchUnblacklist(String accessToken, List<String> openidList) {
        requireNonNull(accessToken);
        requireNonNull(openidList);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/tags/members/batchunblacklist")
                .addQuery("access_token", accessToken)
                .addBody("openid_list", openidList)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("取消拉黑用户", root, CODE_MAP.get("batchUnblacklist"));
    }

    /**
     * 获取公众号的黑名单列表
     * <p>本接口用来获取账号的黑名单列表，黑名单列表由一串 OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param beginOpenid 起始OpenID，为空时从开头拉取
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/usermanage/userinfo/api_getblacklist.html">用户管理 / 用户信息 / 获取公众号的黑名单列表</a>
     */
    public GetBlacklistResponsePayload getBlacklist(String accessToken, String beginOpenid) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/tags/members/getblacklist")
                .addQuery("access_token", accessToken)
                .addBody("begin_openid", StringUtils.defaultString(beginOpenid))
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取公众号的黑名单列表", root, CODE_MAP.get("getBlacklist"));
        return JacksonUtils.deserialize(responseStr, GetBlacklistResponsePayload.class);
    }

    /**
     * 获取用户基本信息
     * <p>在关注者与公众号产生消息交互后，公众号可获得关注者的OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的。对于不同公众号，同一用户的openid不同）。
     * <p>可通过本接口来根据OpenID获取用户基本信息，包括语言和关注时间。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param openid      普通用户的标识，对当前公众号唯一
     * @param lang        返回国家地区语言版本，zh_CN 简体，zh_TW 繁体，en 英语
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/usermanage/userinfo/api_userinfo.html">用户管理 / 用户信息 / 获取用户基本信息</a>
     */
    public UserInfo userInfo(String accessToken, String openid, Lang lang) {
        requireNonNull(accessToken);
        requireNonNull(openid);
        lang = lang != null ? lang : Lang.zh_CN;

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/user/info")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addQuery("openid", openid)
                .addQuery("lang", lang)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取用户基本信息", root, CODE_MAP.get("userInfo"));
        return JacksonUtils.deserialize(responseStr, UserInfo.class);
    }

    /**
     * 批量获取用户基本信息
     * <p>本接口用于批量获取用户基本信息。最多支持一次拉取100条。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param userList    用户列表
     * @return 用户基本信息列表
     */
    public List<UserInfo> batchUserinfo(String accessToken, List<UserSearchParams> userList) {
        requireNonNull(accessToken);
        requireNonNull(userList);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/user/info/batchget")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("user_list", userList)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("批量获取用户基本信息", root, CODE_MAP.get("batchUserinfo"));
        return JacksonUtils.deserializeList(root.get("user_info_list").toPrettyString(), UserInfo.class);
    }

    /**
     * 获取关注用户列表
     * <p>本接口用来获取账号的关注者列表，关注者列表由一串OpenID（加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param nextOpenid  上一批列表的最后一个OPENID，不填默认从头开始拉取
     * @see <a href="https://developers.weixin.qq.com/doc/service/api/usermanage/userinfo/api_getfans.html">用户管理 / 用户信息 / 获取关注用户列表</a>
     */
    public GetFansResponsePayload getFans(String accessToken, String nextOpenid) {
        requireNonNull(accessToken);

        String responseStr = Forest
                .get(config.getWechatGateway() + "/cgi-bin/user/get")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addQuery("next_openid", StringUtils.defaultString(nextOpenid))
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("获取关注用户列表", root, CODE_MAP.get("getFans"));
        return JacksonUtils.deserialize(responseStr, GetFansResponsePayload.class);
    }

    /**
     * 拉黑用户
     * <p>本接口用于拉黑一批用户，黑名单列表由一串 OpenID （加密后的微信号，每个用户对每个公众号的OpenID是唯一的）组成。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param openidList  需要拉入黑名单的用户的openid，一次拉黑最多允许20个
     */
    public void batchBlacklist(String accessToken, List<String> openidList) {
        requireNonNull(accessToken);
        requireNonNull(openidList);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/tags/members/batchblacklist")
                .addQuery("access_token", accessToken)
                .addBody("openid_list", openidList)
                .contentTypeJson()
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("拉黑用户", root, CODE_MAP.get("batchBlacklist"));
    }

    /**
     * 设置用户备注名
     * <p>本接口用于对指定用户设置备注名，该接口暂时开放给微信认证的服务号。
     *
     * @param accessToken 接口调用凭证，可使用 <a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AccessToken.html">access_token</a>、<a href="https://developers.weixin.qq.com/doc/oplatform/developers/dev/AuthorizerAccessToken.html">authorizer_access_token</a>
     * @param openid      用户标识
     * @param remark      新的备注名，长度必须小于30字符
     * @see <a href="https://developers.weixin.qq.com/doc/offiaccount/User_Management/Configuring_user_notes.html">用户管理 > 设置用户备注名</a>
     */
    public void updateRemark(String accessToken, String openid, String remark) {
        requireNonNull(accessToken);
        requireNonNull(openid);
        requireNonNull(remark);

        String responseStr = Forest
                .post(config.getWechatGateway() + "/cgi-bin/user/info/updateremark")
                .contentTypeJson()
                .addQuery("access_token", accessToken)
                .addBody("openid", openid)
                .addBody("remark", remark)
                .connectTimeout(config.getConnectTimeout())
                .readTimeout(config.getReadTimeout())
                .executeAsString();
        JsonNode root = JacksonUtils.readTree(responseStr);
        ResponseUtils.findError("设置用户备注名", root, CODE_MAP.get("updateRemark"));
    }

    private static final Map<String, Map<Long, String>> CODE_MAP;

    static {
        CODE_MAP = new HashMap<>();

        Map<Long, String> codeMap = new HashMap<>();
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40003L, "不合法的 OpenID");
        codeMap.put(40032L, "一次只能取消拉黑20个用户");
        CODE_MAP.put("batchUnblacklist", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40003L, "传入非法的openid");
        codeMap.put(49003L, "传入的openid不属于此AppID");
        CODE_MAP.put("getBlacklist", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40003L, "用户未关注或openid错误");
        codeMap.put(40013L, "AppID无效错误");
        CODE_MAP.put("userInfo", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40003L, "常见于错误的openid(UnionID)，如openid的数据错误，或者数据正确的openid但不属于本公众号");
        codeMap.put(40013L, "AppID无效");
        codeMap.put(40032L, "不合法的 openid 列表长度");
        CODE_MAP.put("batchUserinfo", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取 access_token 时 AppSecret 错误，或者 access_token 无效");
        codeMap.put(40003L, "不合法的 OpenID");
        codeMap.put(40013L, "无效的AppID");
        CODE_MAP.put("getFans", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取access_token时AppSecret错误，或者access_token无效");
        codeMap.put(40003L, "不合法的 OpenID");
        codeMap.put(40032L, "一次只能拉黑20个用户");
        CODE_MAP.put("batchBlacklist", Collections.unmodifiableMap(codeMap));

        codeMap = new HashMap<>();
        codeMap.put(-1L, "系统繁忙，请稍候再试");
        codeMap.put(40001L, "获取access_token时AppSecret错误，或者access_token无效");
        codeMap.put(40003L, "不合法的 OpenID");
        codeMap.put(40013L, "AppID无效错误");
        CODE_MAP.put("updateRemark", Collections.unmodifiableMap(codeMap));
    }

}
